

package com.curluin.rendering;

import javax.microedition.khronos.egl.EGLConfig;
import com.curluin.controll.Controller;
import javax.microedition.khronos.opengles.GL10;

import com.curluin.scene.Node;
import com.curluin.scene.TriangleMesh;
import com.curluin.view.Camera;

import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.util.Log;

/**
 * Implements the 3D rendering functionality.
 * 
 * @author Philipp Jenke
 * 
 */
public class Renderer3D implements GLSurfaceView.Renderer {
	private static final String LOG_TAG = "Renderer";


	/*
	 * List of 3D objects, organized in a scene graph (initialized with an empty
	 * dummy node).
	 */
	public Node rootNode = new Node();

	/**
	 * Camera object.
	 */
	private Camera camera;

	/*
	 * Constructor.
	 */
	public Renderer3D(Camera c) {
		camera = c;
	}

	/*
	 * Event-Handler: surface created. (non-Javadoc)
	 * 
	 * @see
	 * android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(javax.microedition
	 * .khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)
	 */
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		Log.i(LOG_TAG, "onSurfaceCreated()");
		gl.glMatrixMode(GL10.GL_PROJECTION);
		
		final int width = 320;
		final int height = 480;
		GLU.gluPerspective(gl, 45f, (float) width / (float) height, 0.01f, 200f);
		gl.glViewport(0, 0, width, height);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glEnable(GL10.GL_DEPTH_TEST);

		// preparation
		// enable the differentiation of which side may be visible
		gl.glEnable(GL10.GL_CULL_FACE);
		// which is the front? the one which is drawn counter clockwise
		gl.glFrontFace(GL10.GL_CCW);
		// which one should NOT be drawn
		gl.glCullFace(GL10.GL_BACK);

		// preparation
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		// Define the color we want to be displayed as the "clipping wall"
		gl.glClearColor(TriangleMesh.WATER_BLUE.get(0), TriangleMesh.WATER_BLUE.get(1), TriangleMesh.WATER_BLUE.get(2), 1.0f);

		// Enable lighting
		gl.glEnable(GL10.GL_LIGHTING);
		// Set a light
		gl.glEnable(GL10.GL_LIGHT0);
		float lightPosition[] = new float[] { 0.0f, 0.0f, 1.0f, -1.0f };
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition, 0);
		float lightDiffuse[] = new float[] { .5f, .5f, 0.5f, 1f };
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
		float lightAmbient[] = new float[] { .5f, .5f, .5f, 1f };
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);
//		gl.glEnable(GL10.GL_LIGHT0);
//		float lightPosition[] = new float[] { 0.0f, 0.0f, 1.0f, -1.0f };
//		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition,
//				0);
//		float lightDiffuse[] = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
//		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);
//		float lightAmbient[] = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
//		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);

		// Set material
//		float diffuseColor[] = new float[] { 0.25f, 0.75f, 0.25f, 1.0f };
//		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseColor,
//				0);
//		float ambientColor[] = new float[] { 0.1f, 0.1f, 0.1f, 1.0f };
//		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientColor,
//				0);
//		float specularColor[] = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
//		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS,
//				specularColor, 0);

		//Shading
		gl.glShadeModel(GL10.GL_SMOOTH);

		gl.glEnable(GL10.GL_NORMALIZE);

	}

	/**
	 * Event-Handler: surface changed. (non-Javadoc)
	 * 
	 * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.microedition.khronos.opengles.GL10,
	 *      int, int)
	 */
	@Override
	public void onSurfaceChanged(GL10 gl, int w, int h) {
		gl.glViewport(0, 0, w, h);
		GLU.gluPerspective(gl, 45f, (float) w / (float) h, 0.01f, 200f);
	}

	/*
	 * Event-Handler: draw frame event. (non-Javadoc)
	 * 
	 * @see
	 * android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.
	 * khronos.opengles.GL10)
	 */
	@Override
	public void onDrawFrame(GL10 gl) {
		long startTime = System.currentTimeMillis(), endTime, remainingTime;
		
		// clear the color buffer and the depth buffer
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		updateGlView(gl);

		rootNode.render(gl);
		
		// Ensure that rendering is not performed more often than model computation
		endTime = System.currentTimeMillis();
		remainingTime = Controller.TIMER_INTERVAL + 3 - (endTime - startTime);
		if (remainingTime > 0)
			try {
				Thread.sleep(remainingTime);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	/**
	 * Update the GL matrix stack based on the camera.
	 * 
	 * @param gl
	 */
	public void updateGlView(GL10 gl) {
		gl.glLoadIdentity();
		final float eyeX = camera.getEye().get(0);
		final float eyeY = camera.getEye().get(1);
		final float eyeZ = camera.getEye().get(2);
		final float refX = camera.getRef().get(0);
		final float refY = camera.getRef().get(1);
		final float refZ = camera.getRef().get(2);
		final float upX = camera.getUp().get(0);
		final float upY = camera.getUp().get(1);
		final float upZ = camera.getUp().get(2);
		GLU.gluLookAt(gl, eyeX, eyeY, eyeZ, refX, refY, refZ, upX, upY, upZ);
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public Camera getCamera() {
		return camera;
	}

	/**
	 * Setter.
	 * 
	 * @param scene
	 */
	public void setScene(Node scene) {
		rootNode = scene;
	}

	public void destroy() {
		rootNode.destroy();
		rootNode = null;
		camera.destroy();
		camera=null;
	}
}
